package com.example.eeuse.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.eeuse.mapper.DocumentMapper;
import com.example.eeuse.model.Document;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.easyes.annotation.rely.Analyzer;
import org.dromara.easyes.annotation.rely.FieldType;

import org.dromara.easyes.common.constants.BaseEsConstants;
import org.dromara.easyes.core.biz.CreateIndexParam;
import org.dromara.easyes.core.biz.EntityInfo;
import org.dromara.easyes.core.conditions.index.LambdaEsIndexWrapper;
import org.dromara.easyes.core.toolkit.EntityInfoHelper;
import org.dromara.easyes.core.toolkit.IndexUtils;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.ingest.PutPipelineRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;


/**
 * 如何用Elasticsearch实现Word、PDF，TXT文件的全文内容检索？
 * https://mp.weixin.qq.com/s/aLD-rbJCXWK8K2aJWj-DYA
 *
 */
@Slf4j
@Api(tags = "Client原生ES使用")
@RestController
@RequestMapping("/doc")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DocEsController {

    private final RestHighLevelClient client;

    @ApiOperation(value = "文档上传入库")
    @PostMapping("/uploadFile")
    public IndexResponse uploadFile(@RequestPart MultipartFile file, @RequestParam String indexName) throws IOException {

        String originalFilename = file.getOriginalFilename();

        String fileName = FileUtil.mainName(originalFilename);
        String fileExtension = FileUtil.getSuffix(originalFilename);

        // String path = "src/main/resources/static/成飞用户集成.pdf";
//        Path path = Paths.get("src/main/resources/static/file/" + fileName + "." + fileExtension);
        String user_dir = System.getProperty("user.dir");

        String fileId = IdUtil.fastSimpleUUID();
        String yearMonth = DateUtil.format(new DateTime(), DatePattern.SIMPLE_MONTH_FORMAT);

        File files = FileUtil.createTempFile("file-es-", String.format(".%s", fileExtension), new File(user_dir + "/target/temp"), true);
        //File files = new File(user_dir + "/src/main/resources/static/file/" + String.format("%s/%s.%s", yearMonth, fileId, fileExtension));
        file.transferTo(files);

        log.info("{}, {}, {}", indexName, files.getName(), files.getPath());

        byte[] bytes = FileUtil.readBytes(files);
        //byte[] bytes = getContent(files);
        String base64 = Base64.encode(bytes);


        Document document = new Document();
        document.setId(fileId);
        document.setTitle("测试文件");
        document.setName(originalFilename);
        document.setContent(base64); //文件的data 64编码
        document.setAttachment("encode");

        log.info("{}, {}", indexName, document);

        return upload(document, indexName);
    }



    /**
     *
     *
     */
    @ApiOperation(value = "文档入库")
    @PostMapping("/upload")
    public IndexResponse upload(@RequestBody Document file, @RequestParam String indexName) throws IOException {

        if (StringUtils.isBlank(indexName)) {
            indexName = "createindex_2014_doc";
        }

        IndexRequest indexRequest = new IndexRequest( indexName);
        //indexRequest.id(file.getId());

        String dcoId = IdUtil.fastSimpleUUID();
        indexRequest.id(dcoId);

        file.setId(dcoId);
        System.out.println(file.getName());

        //上传同时，使用attachment pipline进行提取文件
        if (StringUtils.isBlank(file.getAttachment())) {
            String encodeStr = Base64.encode(file.getContent().getBytes(StandardCharsets.UTF_8));
            file.setContent(encodeStr); //文件的data 64编码
        }

        indexRequest.source(JSONUtil.toJsonStr(file), XContentType.JSON);
        indexRequest.setPipeline("attachment");
        IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);

        System.out.println(indexResponse);

        return indexResponse;
    }

    private final DocumentMapper documentMapper;


    /**
     *  PUT /docwrite
     *  {
     *     "mappings": {
     *         "properties": {
     *             "id": {
     *                 "type": "keyword"
     *             },
     *             "name": {
     *                 "type": "text",
     *                 "analyzer": "ik_max_word"
     *             },
     *             "type": {
     *                 "type": "keyword"
     *             },
     *             "attachment": {
     *                 "properties": {
     *                     "content": {
     *                         "type": "text",
     *                         "analyzer": "ik_smart"
     *                     }
     *                 }
     *             }
     *         }
     *     }
     * }
     *
     * @param indexName
     * @return
     */
    @ApiOperation(value = "创建索引")
    @GetMapping("/createIndex")
    public Boolean createIndex(String indexName) {

        // 使用 IndexWrapper 实体对象创建

        LambdaEsIndexWrapper<Document> wrapper = new LambdaEsIndexWrapper<>();
        // 此处简单起见 索引名称须保持和实体类名称一致,字母小写 后面章节会教大家更如何灵活配置和使用索引
        wrapper.indexName(Document.class.getSimpleName().toLowerCase());
        if (StringUtils.isNotBlank(indexName)) {
            wrapper.indexName(indexName);
        }

        // 此处将文章标题映射为keyword类型(不支持分词),文档内容映射为text类型,可缺省
        // 支持分词查询,内容分词器可指定,查询分词器也可指定,,均可缺省或只指定其中之一,不指定则为ES默认分词器(standard)
//        wrapper.mapping(Document::getTitle, FieldType.KEYWORD)
//                .mapping(Document::getName, FieldType.KEYWORD,  Analyzer.IK_MAX_WORD)
//                .mapping(Document::getType, FieldType.KEYWORD)
//                .mapping(Document::getContent, FieldType.TEXT, Analyzer.IK_MAX_WORD,Analyzer.IK_MAX_WORD);

        JSONObject mapping = new JSONObject()
                .set("name", new JSONObject().set("type", FieldType.TEXT.getType()).set("analyzer", Analyzer.IK_MAX_WORD))
                .set("type", new JSONObject().set("type", FieldType.KEYWORD.getType()))
                .set("attachment", new JSONObject().set("properties", new JSONObject()
                        .set("content", new JSONObject().set("type", FieldType.TEXT.getType()).set("analyzer", Analyzer.IK_SMART))));

        // mappings 最外层必须是 properties
        wrapper.mapping(new JSONObject().set(BaseEsConstants.PROPERTIES, mapping));
        // 如果上述简单的mapping不能满足你业务需求,可自定义mapping
        // wrapper.mapping(Map);

        // 设置分片及副本信息,3个shards,2个replicas,可缺省
        wrapper.settings(3,2);

        // 如果上述简单的settings不能满足你业务需求,可自定义settings
        // wrapper.settings(Settings);

        // 设置别名信息,可缺省
        String aliasName = "daily";
        wrapper.createAlias(aliasName);

        // 创建索引
        return  documentMapper.createIndex(wrapper);
    }



    @ApiOperation(value = "创建索引2")
    @GetMapping("/createIndex2")
    public Boolean createIndex2(String indexName) {
        // indexName-- 只能使用小写
        // 使用 Document 实体对象创建
        EntityInfo entityInfo = EntityInfoHelper.getEntityInfo(Document.class);
        CreateIndexParam createIndexParam = IndexUtils.getCreateIndexParam(entityInfo, Document.class);
        if (StringUtils.isNotBlank(indexName)) {
            createIndexParam.setIndexName(indexName);
        }
        return IndexUtils.createIndex(client, entityInfo, createIndexParam);
    }



    /**
     *
     * {
     *     "description": "Attachment index pipeline",
     *     "processors": [
     *         {
     *             "attachment": {
     *                 "field": "content",
     *                 "ignore_missing": true
     *             },
     *             "remove": {
     *                 "field": "content"
     *             }
     *         }
     *     ]
     * }
     *
     */
    @ApiOperation(value = "创建管道-attachment")
    @PostMapping("/pipelineAttachment")
    public AcknowledgedResponse pipelineAttachment() {

        // 1. 创建一个高级客户端对象
        try {

            JSONObject pipelineAttachment = new JSONObject().set("description", "Attachment index pipeline")
                    .set("processors", new JSONObject[]{new JSONObject().set("attachment", new JSONObject().set("field", "content").set("ignore_missing", true))
                            .set("remove", new JSONObject().set("field", "content"))});

            BytesArray bytesArray = new BytesArray(pipelineAttachment.toString().getBytes(StandardCharsets.UTF_8));

//            XContentBuilder builder = XContentFactory.jsonBuilder();
//            builder.map(pipelineAttachment);
//            BytesReference pipelineSource = BytesReference.bytes(builder);
//            XContentType xContentType = builder.contentType();
//            new PutPipelineRequest("attachment", pipelineSource, xContentType);


            // 2. 创建一个新的pipeline对象
            PutPipelineRequest request = new PutPipelineRequest("attachment", bytesArray, XContentType.JSON);
            System.out.println("request = " + request);

//            // 3. 使用客户端发送请求
            AcknowledgedResponse response = client.ingest().putPipeline(request, RequestOptions.DEFAULT);
//
//            // 4. 处理响应
            System.out.println(response.isAcknowledged());
            return response;
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }


    /**
     * 根据关键词，搜索对应的文件信息
     * 查询文件中的文本内容
     * @param  keyword  关键字
     */
    @ApiOperation(value = "文档查询")
    @GetMapping("/search")
    public SearchHits search(String keyword, @RequestParam String indexName) throws IOException {

        if (StringUtils.isBlank(indexName)) {
            indexName = "createindex_2014_doc";
        }

        SearchRequest searchRequest = new SearchRequest(indexName.trim());

        //默认会search出所有的东西来
//        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

        //使用lk分词器查询，会把插入的字段分词，然后进行处理
        SearchSourceBuilder srb = new SearchSourceBuilder();
        srb.query(QueryBuilders.matchQuery("attachment.content", keyword).analyzer("ik_smart"));
//        srb.query(QueryBuilders.termQuery("attachment.content", keyword));

        //设置highlighting
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        HighlightBuilder.Field highlightContent = new HighlightBuilder.Field("attachment.content");
        highlightContent.highlighterType();
        highlightBuilder.field(highlightContent);
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");

        //highlighting会自动返回匹配到的文本，所以就不需要再次返回文本了
        String[] includeFields = new String[]{"name"};
        String[] excludeFields = new String[]{"attachment.content"};
        srb.fetchSource(includeFields, excludeFields);

        //把刚才设置的值导入进去
        srb.highlighter(highlightBuilder);
        searchRequest.source(srb);
        SearchResponse res = client.search(searchRequest, RequestOptions.DEFAULT);

        //获取hits，这样就可以获取查询到的记录了
        SearchHits hits = res.getHits();

        List<SearchHit> list = new ArrayList<>();

        //hits是一个迭代器，所以需要迭代返回每一个hits
        Iterator<SearchHit> iterator = hits.iterator();
        int count = 0;
        while (iterator.hasNext()) {
            SearchHit hit = iterator.next();

            list.add(hit);

            //获取返回的字段
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            System.out.println(sourceAsMap.get("name"));

            //统计找到了几条
            count++;

            //这个就会把匹配到的文本返回，而且只返回匹配到的部分文本
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            System.out.println(highlightFields);

        }


        System.out.println("查询到" + count + "条记录");

        return hits;
    }


}
